//https://iotservice.iot.aliyun.com/
#ifdef ESP8266
#include <WiFiClient.h>
#include <ESP8266WiFi.h>
#endif // ESP8266

#ifdef ESP32
#include <WiFi.h>
#endif // ESP32

#include <Wire.h>
#include <SPI.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <U8g2lib.h>

//3者只可以选择一种
#define DEBUG_ALL//显示所有问题
//#define DEBYG_MAIN//显示主要问题
//#define DEBUG_NONE//不显示任何信息

/* 连接您的WIFI SSID和密码 */
#define WIFI_SSID        "***"//替换自己的WIFI
#define WIFI_PASSWD      "***"//替换自己的WIFI

/* 设备密钥信息*/
#define PRODUCT_KEY       "***"
#define DEVICE_NAME       "***"
#define DEVICE_SECRET     "***"
#define REGION_ID         "cn-shanghai"
#define CLIENT_ID         "***"
#define MQTT_PASSWD       "***"

/* 线上环境域名和端口号，不需要改 */
#define MQTT_SERVER       PRODUCT_KEY ".iot-as-mqtt." REGION_ID ".aliyuncs.com"
#define MQTT_PORT         1883
#define MQTT_USRNAME      DEVICE_NAME "&" PRODUCT_KEY
#define ALINK_BODY_FORMAT         "{\"id\":\"laohuajia-001\",\"version\":\"1.0.0\",\"method\":\"thing.event.property.post\",\"params\":%s}"
#define ALINK_TOPIC_PROP_POST     "/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/event/property/post"

#define SENSOR_1 D1//传感器1连接引脚
#define SENSOR_2 D2//传感器2连接引脚
#define REALY LED_BUILTIN//继电器连接引脚

//用户自定义，请按照实际环境进行修改
float regionSize = 5.0;//检测的总面积
int setAllowAllPeopleNum = 10;//范围内允许的最大人数

//系统核心数据，请勿修改
int inputNum = 0;//进入人数
int outNum = 0;//出去人数
int  headcountNum = 0;//记录当前区域总人数
unsigned long lastUpdataTime = 0;//上一次向云端发送数据的时间
unsigned long lastGatherTime = 0;//上一次采集数据的时间
bool manualMode = false;//手动模式
bool displayStade = false;//继电器显示状态，防止一直显示
bool ledState = false;//默认灯关闭

WiFiClient espClient;//创建tcp服务对象
PubSubClient  client(espClient);//创建mqqt服务对象
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0 , /* reset=*/ U8X8_PIN_NONE , /* clock=*/ D5 , /* data=*/ D6);

void setup()
{
    pinMode(SENSOR_1 , INPUT);//传感器1
    pinMode(SENSOR_2 , INPUT);//传感器2
    pinMode(REALY , OUTPUT);//继电器
    digitalWrite(REALY , HIGH);//默认关闭继电器

#ifndef DEBUG_NONE
    Serial.begin(115200);
#endif // DEBUG_NONE

    Serial.println("Start");

    wifiInit();//尝试连接wifi
    mqttCheckConnect();//wifi连接成功后尝试连接mqqt服务器
    u8g2.begin();
    u8g2.enableUTF8Print();
    u8g2.setFont(u8g2_font_unifont_t_chinese2);
    u8g2.setFontDirection(0);
    u8g2.clearBuffer();
}

//连接wifi
void wifiInit()
{
    WiFi.begin(WIFI_SSID , WIFI_PASSWD);
    while ( WiFi.status() != WL_CONNECTED )
    {
        delay(1000);
        Serial.println("WiFi not Connect");
    }

    Serial.println("Connected to AP");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
    Serial.print("espClient [");
    client.setServer(MQTT_SERVER , MQTT_PORT);   /* 连接WiFi之后，连接MQTT服务器 */
    client.setCallback(callback);//收到服务器信息回调函数
}

//连接mttq服务器
void mqttCheckConnect()
{
    while ( !client.connected() )
    {
        Serial.println("Connecting to MQTT Server ...");
        if ( client.connect(CLIENT_ID , MQTT_USRNAME , MQTT_PASSWD) )

        {

            Serial.println("MQTT Connected!");
            subscribeTopic(); // 订阅指定主题

        } else
        {
            Serial.print("MQTT Connect err:");
            Serial.println(client.state());
            delay(5000);
        }
    }
}

//订阅指定主题
void subscribeTopic()
{
    String topicString = "/";
    topicString += PRODUCT_KEY;
    topicString += "/";
    topicString += DEVICE_NAME;
    topicString += "/user/switch";

    if ( client.subscribe(topicString.c_str()) )
    {

        Serial.println("Subscrib Topic:");

        Serial.println(topicString);

    } else
    {

        Serial.print("Subscribe Fail...");

    }

}

//mqqt回调函数
void callback(char* topic , byte* payload , unsigned int length)
{
    String topic_ = topic;
    if ( topic_ == "/sys/gkfrEFSmLm5/device_1/thing/event/property/post" )
    {
        Serial.println("向服务器上传信息");
    } else if ( topic_ == "/sys/gkfrEFSmLm5/device_1/thing/event/property/post_reply" )
    {
        Serial.println("服务器应答");
    } else if ( topic_ == "/gkfrEFSmLm5/device_1/user/switch" )
    {
        Serial.println("更新继电器状态");
        if ( payload[0] == '1' )
        {
            Serial.println("打开继电器");
        #ifdef ESP8266
            if ( REALY == LED_BUILTIN )
            {
                digitalWrite(REALY , LOW);//打开继电器
                ledState = true;
            } else
            {
                digitalWrite(REALY , HIGH);//打开继电器
                ledState = true;
            }
        #endif // ESP8266
        #ifndef ESP8266
            digitalWrite(REALY , HIGH);//打开继电器
            ledState = true;
        #endif // ESP8266

        } else if ( payload[0] == '0' )
        {
            Serial.println("关闭继电器");
        #ifdef ESP8266
            if ( REALY == LED_BUILTIN )
            {
                digitalWrite(REALY , HIGH);//关闭继电器
                ledState = false;
            } else
            {
                digitalWrite(REALY , LOW);//关闭继电器
                ledState = false;
            }
        #endif // ESP8266
        #ifndef ESP8266
            digitalWrite(REALY , LOW);//关闭继电器
        #endif // ESP8266
        } else
        {
            Serial.println("错误指令");
        }
        return;
    } else
    {
        //Serial.println("未知指令");
        String retData;
        for ( int i = 0; i < length; i++ )
        {

            retData += (char) payload[i];

        }

        StaticJsonDocument<192> doc;

        DeserializationError error = deserializeJson(doc , retData);

        if ( error )
        {
            Serial.print("返回数据非JOSN格式/未知指令");
            Serial.println(error.f_str());
            return;
        }

        //const char* method = doc["method"]; // "thing.service.property.set"
        const char* id = doc["id"]; // "857803000"

        int params_jdq_switch = doc["params"]["jdq_switch"]; // 1
        manualMode = true;
        //const char* version = doc["version"]; // "1.0.0"
        Serial.print("命令ID:");
        Serial.println(id);
        Serial.print("命令内容:");
        Serial.println(params_jdq_switch);
        ledState = params_jdq_switch;
        digitalWrite(REALY , !params_jdq_switch);
        //立即上报设备状态给服务器
        mqttIntervalPost();
    }



    Serial.println("");
}

//向mqqt服务器发送信息
void mqttIntervalPost()
{
    //写入信息
    String output;
    StaticJsonDocument<192> doc;

    doc["method"] = "thing.event.property.post";
    doc["id"] = "1111";//自定义消息id，允许修改

    JsonObject params = doc.createNestedObject("params");
    params["trafficDensity"] = ( headcountNum / regionSize );
    params["outNum"] = outNum;
    params["inputNum"] = inputNum;
#ifdef ESP8266
    if ( REALY == LED_BUILTIN )
    {
        params["jdq_switch"] = (int)ledState;
       /* Serial.print("led:");
        Serial.println(digitalRead(REALY));*/
    } else
    {
        params["jdq_switch"] = digitalRead(REALY);
    }
#endif // ESP8266
#ifndef ESP8266
    params["jdq_switch"] = digitalRead(REALY);
#endif // ESP8266

    params["regionsPeopleNum"] = ( inputNum - outNum );
    doc["version"] = "1.0.0";

    serializeJson(doc , output);

    boolean d = client.publish(ALINK_TOPIC_PROP_POST , output.c_str());
    Serial.print("publish:0=失败;1=成功    publish:");
    Serial.println(d);
#ifdef DEBUG_ALL
    Serial.println(output);
#endif // DEBUG_ALL
}

void loop()
{
    if ( millis() - lastGatherTime >= 1000 )
    {
        lastGatherTime = millis();
        /*Serial.println(digitalRead(SENSOR_1));
        Serial.println(digitalRead(SENSOR_2));*/
        if ( digitalRead(SENSOR_1) )
        {
            //进人 人数加一
            headcountNum++;
            inputNum++;
            String data[4];
        #ifdef DEBUG_ALL
            Serial.print("进入人数：");//打印信息
            Serial.println(inputNum);//打印信息
            Serial.print("出去人数：");//打印信息
            Serial.println(outNum);//打印信息
            Serial.print("人流密度：");//打印信息
            Serial.print(headcountNum / regionSize);//打印信息
            Serial.println("人/平方米");//打印信息
            Serial.print("当前人数 ：");//打印信息
            Serial.println(inputNum - outNum);//打印信息
        #endif // DEBUG_ALL

            data[0] = "进入人数:";
            data[0] += inputNum;
            data[1] = "出去人数:";
            data[1] += outNum;
            data[2] = "人流密度:";
            data[2] += headcountNum / regionSize;
            data[2] += "人/平方米";
            data[3] = "当前人数:";
            data[3] += inputNum - outNum;

            //判断是否超出设置的最大人数
            if ( ( ( inputNum - outNum ) > setAllowAllPeopleNum ) && !manualMode )
            {
                //超过 打开继电器
                if ( !displayStade )
                {
                    Serial.println("超过设定人数，打开继电器");
                #ifdef ESP8266
                    if ( REALY == LED_BUILTIN )
                    {
                        digitalWrite(REALY , LOW);//打开继电器
                        ledState = true;
                    } else
                    {
                        digitalWrite(REALY , HIGH);//打开继电器
                        ledState = true;
                    }
                #endif // ESP8266
                #ifndef ESP8266
                    digitalWrite(REALY , HIGH);//打开继电器
                #endif // ESP8266
                    displayStade = true;
                    ledState = true;
                }

            }

            //打印到屏幕
            u8g2.clearBuffer();
            u8g2.setCursor(0 , 15);
            u8g2.print(data[0]);
            u8g2.setCursor(0 , 30);
            u8g2.print(data[1]);
            u8g2.setCursor(0 , 45);
            u8g2.print(data[2]);
            u8g2.setCursor(0 , 60);
            u8g2.print(data[3]);
            u8g2.sendBuffer();
        }
        if ( digitalRead(SENSOR_2) )
        {
            //出人 人数减一
            headcountNum--;
            outNum++;
            String data[4];
        #ifdef DEBUG_ALL
            Serial.print("进入人数：");//打印信息
            Serial.println(inputNum);//打印信息
            Serial.print("出去人数：");//打印信息
            Serial.println(outNum);//打印信息
            Serial.print("人流密度：");//打印信息
            Serial.print(headcountNum / regionSize);//打印信息
            Serial.println("人/平方米");//打印信息
            Serial.print("当前人数 ：");//打印信息
            Serial.println(inputNum - outNum);//打印信息
        #endif // DEBUG_ALL

            data[0] = "进入人数:";
            data[0] += inputNum;
            data[1] = "出去人数:";
            data[1] += outNum;
            data[2] = "人流密度:";
            data[2] += headcountNum / regionSize;
            data[2] += "人/平方米";
            data[3] = "当前人数:";
            data[3] += inputNum - outNum;

            //判断是否超出设置的最大人数
            if ( ( ( inputNum - outNum ) <= setAllowAllPeopleNum ) && !manualMode )
            {
                //超过 打开继电器
                if ( displayStade )
                {
                    Serial.println("小于设定人数，关闭继电器");
                #ifdef ESP8266
                    if ( REALY == LED_BUILTIN )
                    {
                        digitalWrite(REALY , HIGH);//关闭继电器
                        ledState = false;
                    } else
                    {
                        digitalWrite(REALY , LOW);//关闭继电器
                        ledState = false;
                    }
                #endif // ESP8266
                #ifndef ESP8266
                    digitalWrite(REALY , LOW);//关闭继电器
                    ledState = false;
                #endif // ESP8266
                    displayStade = false;

                }
            }

            //打印到屏幕
            u8g2.clearBuffer();
            u8g2.setCursor(0 , 15);
            u8g2.print(data[0]);
            u8g2.setCursor(0 , 30);
            u8g2.print(data[1]);
            u8g2.setCursor(0 , 45);
            u8g2.print(data[2]);
            u8g2.setCursor(0 , 60);
            u8g2.print(data[3]);
            u8g2.sendBuffer();
        }
    }

    if ( millis() - lastUpdataTime >= 15000 )
    {
        lastUpdataTime = millis();

        /* 上报消息心跳周期 */
        mqttIntervalPost();
    }

    client.loop();
}
